
/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.sys>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: utils.cpp
 *
 * Purpose: helper function implementation
 *
 * Developer:
 *   wen.gu , 2020-07-29
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "utils.h"

#include <map>

#include "key_word.h"
#include "log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
/**key: the string of type in sidl, val: the enum of type category */
using DataTypeMap = std::map<std::string, DataType>;


/**key: the string of type in sidl, val: the string of type in cpp*/
using DataTypeStrMap = std::map<std::string, std::string>;

/** key: data type define in enum, val: the string of type in cpp */
using DataTypeStrMap2 = std::map<DataType, std::string>;

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

static const DataTypeMap gBaseType = 
{
    {KW_TYPE_STRING,   DataType::kString},
    {KW_TYPE_BOOL,     DataType::kBool},
    {KW_TYPE_INT8,     DataType::kInt8},
    {KW_TYPE_INT16,    DataType::kInt16},
    {KW_TYPE_INT32,    DataType::kInt32},
    {KW_TYPE_INT64,    DataType::kInt64},
    {KW_TYPE_UINT8,    DataType::kUInt8},
    {KW_TYPE_UINT16,   DataType::kUInt16},
    {KW_TYPE_UINT32,   DataType::kUInt32},
    {KW_TYPE_UINT64,   DataType::kUInt64},
    {KW_TYPE_FLOAT,    DataType::kFloat},
    {KW_TYPE_DOUBLE,   DataType::kDouble}
};


static const DataTypeMap gCompositeType = 
{
    {KW_TYPE_STRUCT,   DataType::kStruct},
    {KW_TYPE_VECTOR,   DataType::kVector},
    {KW_TYPE_ENUM,     DataType::kEnum},
    {KW_TYPE_ARRAY,    DataType::kArray},
    {KW_TYPE_MAP,      DataType::kMap},
};

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
static inline DataType Str2DataType_l(const DataTypeMap& dtm, const std::string& str)
{
    DataTypeMap::const_iterator it = dtm.find(str);

    if (it != dtm.end())
    {
        return it->second;
    }

    return DataType::kUnknown;    
}

DataType Str2BaseType(const std::string& type)
{
    return Str2DataType_l(gBaseType, type);
}

DataType Str2CompositeType(const std::string& type)
{
    return Str2DataType_l(gCompositeType, type);
}

DataType Str2DataType(const std::string& type)
{
    DataType dt = Str2BaseType(type);
    if (dt == DataType::kUnknown)
    {
        dt = Str2CompositeType(type);
    }

    return dt;
}

bool HasMember(const YAML::Node& nd, const std::string& key)
{
    return nd[key].IsDefined();
}



bool HasTypeDef(const DataTypeArray& dta, const std::string& type)
{
    if (Str2BaseType(type) != DataType::kUnknown)
    { /** the type is base type */
        return true;
    }


    for (uint32_t i =0; i < dta.size(); i++)
    {/** the type is already parsed composition type */
        const DataTypeInfo& dti = dta[i];
        if (dti.name == type)
        {
            return true;
        }
    }

    return false;    
}

void ParseComment(const YAML::Node& nd, std::string& comment)
{
    if (HasMember(nd, KW_COMMENT))
    {
        YAML::Node nd_comment = nd[KW_COMMENT];

        if (nd_comment.IsScalar())
        {
            comment = nd_comment.as<std::string>();
        }
    }    
}


void PrintError(const YAML::Node& nd, const std::string& err)
{
    YAML::Mark mk = nd.Mark();
    LOGE("error: %s, at line: %d, column: %d\n", err.c_str(), mk.line, mk.column);
}

void PrintWarning(const YAML::Node& nd, const std::string& wrn)
{
    YAML::Mark mk = nd.Mark();
    LOGW("warning: %s, at line: %d, column: %d\n", wrn.c_str(), mk.line, mk.column);
}

void PrintTypeError(const YAML::Node& nd, const std::string& type, const std::string& name, const std::string& err)
{
    YAML::Mark mk = nd.Mark();
    LOGE("define: %s %s,  error: %s, at line: %d, column: %d\n",type.c_str(), name.c_str(), err.c_str(), mk.line, mk.column);    
}


bool ParseDataElement(const DataTypeArray& type_defs, DataTypeElement& dte, const YAML::Node& nd_name, const YAML::Node& nd_type)
{
    if (!nd_name.IsScalar() || !nd_type.IsScalar())
    {
        PrintError(nd_name, "the data element node(name and type) must be scalar");
        return false;            
    }
    
    dte.type = nd_type.as<std::string>();
    if (!HasTypeDef(type_defs, dte.type))
    {
        PrintTypeError(nd_type, "the data type: ", dte.type, " is invailid(maybe not define before current line)");
        return false;
    }

    dte.name = nd_name.as<std::string>();   

    return true;
}

bool ParseDataElementArray(const DataTypeArray& type_defs, DataTypeElements& dta, const YAML::Node& nd)
{
    for (YAML::Node::const_iterator it = nd.begin(); it != nd.end(); ++it )
    {
        DataTypeElement dte;
        if(!ParseDataElement(type_defs, dte, it->first, it->second))
        {
            return false;
        }

        dta.push_back(std::move(dte)); 
    } 

    return true;  
}
